
package cz.czu.pef.DataModelling.beans;

import java.io.*;
import java.util.*;
import java.util.logging.Logger;

import javax.faces.context.*;

import org.ajax4jsf.model.*;
// import org.richfaces.component.*;
import org.richfaces.model.*;


public class PagingExtendedTableDataModel<T> extends ExtendedDataModel<T> implements Serializable, Arrangeable
{

	private static final long serialVersionUID = -2737703810164059155L;
	
	private static final Logger logger = Logger.getLogger(PagingExtendedTableDataModel.class.getName());

	private DataProvider<T> dataProvider;
	private ArrangeableState arrangeableState;
	private Object rowKey;
	private List<Object> cachedKeys;
	private Map<Object, T> cachedData;
	private Integer rowCount;
	private Integer rowIndex;


	public PagingExtendedTableDataModel(DataProvider<T> dataProvider)
	{
		cachedKeys = null;
		cachedData = new HashMap<Object, T>();
		rowCount = null;
		rowIndex = Integer.valueOf(-1);
		rowKey = null;

		this.dataProvider = dataProvider;
	}

	@Override
	public Object getRowKey()
	{
		return rowKey;
	}

	@Override
	public void setRowKey(Object key)
	{
		rowKey = key;
	}

	@Override
	public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument)
	{
		try
		{
			synchronized (this)
			{
				logger.finest("walk():: Enter");

				int rowC = getRowCount();
				int firstRow = ((SequenceRange) range).getFirstRow();
				int numberOfRows = ((SequenceRange) range).getRows();
				if (numberOfRows <= 0)
				{
					numberOfRows = rowC;
				}

				if (cachedKeys != null && !cachedKeys.isEmpty())
				{
					Object key;
					for (Iterator<Object> it = cachedKeys.iterator(); it.hasNext(); visitor.process(context, key, argument))
					{

						key = it.next();
						setRowKey(key);
					}

				}
				else
				{
					cleanDataProviderCache();

					int endRow = firstRow + numberOfRows;
					if (endRow > rowC)
					{
						endRow = rowC;
					}

					Object key;
					for (Iterator<T> it = loadData(firstRow, endRow).iterator(); it.hasNext(); visitor.process(context, key, argument))
					{
						if (cachedKeys == null || cachedData == null)
							break;
						T item = it.next();
						key = getKey(item);
						cachedKeys.add(key);
						cachedData.put(key, item);
					}
				}
			}
		}
		finally
		{
			logger.finest("walk():: Exit");
		}
	}

	protected List<T> loadData(int startRow, int endRow)
	{
		logger.info("loadData():: Loading data from startRow(" + startRow + ") to endRow(" + endRow + ")");
		if (startRow < 0)
		{
			startRow = 0;
			throw new IllegalArgumentException((new StringBuilder()).append("Illegal start index value: ").append(startRow).toString());
		}
		int rowCount = getRowCount();
		if (endRow > rowCount)
		{
			endRow = rowCount;
			throw new IllegalArgumentException((new StringBuilder()).append("Illegal end index value: ").append(endRow).toString());
		}

		return dataProvider.getItemsByRange(startRow, endRow);
	}

	public int getRowCount()
	{
		if (rowCount == null)
		{
			rowCount = new Integer(dataProvider.getRowCount());
		}
		else
		{
			return rowCount.intValue();
		}

		return rowCount.intValue();
	}

	@Override
	public T getRowData()
	{
		if (rowKey == null)
		{
			return null;
		}

		return getObjectByKey(rowKey);
	}

	public Object getKey(T o)
	{
		return dataProvider.getKey(o);
	}

	public T getObjectByKey(Object key)
	{
		T t = cachedData.get(key);
		if (t == null)
		{
			t = dataProvider.getItemByKey(key);
			cachedData.put(key, t);
		}
		return t;
	}

	public int getRowIndex()
	{
		return rowIndex.intValue();
	}

	public void setRowIndex(int rowIndex)
	{
		this.rowIndex = Integer.valueOf(rowIndex);
	}

	public Object getWrappedData()
	{
		throw new UnsupportedOperationException();
	}

	public void setWrappedData(Object data)
	{
		throw new UnsupportedOperationException();
	}

	public boolean isRowAvailable()
	{
		return getRowData() != null;
	}

	/*
	public void reset()
	{
		synchronized (this)
		{
			wrappedKeys = null;
			wrappedData.clear();
			rowCount = null;
			rowIndex = Integer.valueOf(-1);
			rowKey = null;

			updateDataProvider();
		}
	}
	*/

	public DataProvider<T> getDataProvider()
	{
		return dataProvider;
	}

	public void setDataProvider(DataProvider<T> dataProvider)
	{
		this.dataProvider = dataProvider;
	}

	// TODO: RichFaces 4 doesn't need this anymore...
	/*
	public void modify(List<FilterField> filterFields, List<SortField> sortFields)
	{
		this.filterFields = filterFields;
		this.sortFields = sortFields;

		reset();
	}
	*/

	/*
	private void updateDataProvider()
	{
		if (dataProvider instanceof Sortable)
		{
			((Sortable) dataProvider).setSortFields(sortFields);
		}

		if (dataProvider instanceof Filterable)
		{
			((Filterable) dataProvider).setFilterFields(filterFields);
		}
	}
	*/

	@Override
	public void arrange(FacesContext context, ArrangeableState state)
	{
		try
		{
			logger.finest("arrange():: Entry");
			arrangeableState = state;
			if (arrangeableState != null)
			{
				logger.finer("arrange():: Updating dataProvider settings");
				//dataProvider.setSortFields(arrangeableState.getSortFields());
				//dataProvider.setFilterFields(arrangeableState.getFilterFields());
				dataProvider.setLocale(arrangeableState.getLocale());
			}
		}
		finally
		{
			logger.finest("arrange():: Exit");
		}
	}

	protected ArrangeableState getArrangeableState()
	{
		return arrangeableState;
	}

	public void cleanDataProviderCache()
	{
		cachedKeys = new ArrayList<Object>();
		cachedData = new HashMap<Object, T>();
	}
}
